package com.linktco.common.utils;


import com.alibaba.fastjson.JSON;
import lombok.AllArgsConstructor;
import lombok.Data;
import org.apache.commons.lang3.StringUtils;
import org.springblade.core.log.exception.ServiceException;
import org.springframework.util.Assert;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.*;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.time.temporal.TemporalAmount;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 日期工具类
 *
 * @author zhangnx
 */
public class DateUtils {

    public static final String H_M = "HH:mm";
    public static final String H_M_S = "HH:mm:ss";
    public static final String Y_M_D = "yyyy-MM-dd";
    public static final String Y_M_D_H_M = "yyyy-MM-dd HH:mm";
    public static final String Y_M_D_H_M_S = "yyyy-MM-dd HH:mm:ss";
    public static final String Y_M_D_H_M_S_S = "yyyy-MM-dd HH:mm:ss.SSS";
    public static final String YMD = "yyyyMMdd";
    public static final String YMDHMS = "yyyyMMddHHmmss";
    public static final String YMDHMSS = "yyyyMMddHHmmssSSS";

    public static final String dateTemplates[] = {
            "yyyy-MM-dd HH:mm:ss",
            "yyyy年MM月dd日 HH点mm分ss秒",
            "yyyy/MM/dd HH:mm:ss",
            "yyyy.MM.dd HH:mm:ss",
            "yyy年MM月dd日",
            "yyy-MM-dd",
            "yyyyMMdd"};



    /**
     * 格式化日期
     *
     * @param date
     * @return
     */
    public static String formatDateTime(Object date) {
        return format(date, Y_M_D_H_M_S);
    }

    public static String formatTime(Object date) {
        return format(date, H_M_S);
    }

    public static String formatDate(Object date) {
        return format(date, Y_M_D);
    }

    public static String format(Object date, String formatStr) {
    	if (date.getClass().equals(Date.class)){
			SimpleDateFormat sdf = new SimpleDateFormat(formatStr);
			return sdf.format(date);
		}else if (date.getClass().equals(LocalDateTime.class)){
			DateTimeFormatter dtf2 = DateTimeFormatter.ofPattern(formatStr);
			return dtf2.format((LocalDateTime)date);
		}
        return null;
    }


    public static Date plusYears(Date date, int yearsToAdd) {
        return set(date, 1, yearsToAdd);
    }

    public static Date plusMonths(Date date, int monthsToAdd) {
        return set(date, 2, monthsToAdd);
    }

    public static Date plusWeeks(Date date, int weeksToAdd) {
        return plus(date, Period.ofWeeks(weeksToAdd));
    }

    public static Date plusDays(Date date, long daysToAdd) {
        return plus(date, Duration.ofDays(daysToAdd));
    }

    public static Date plusHours(Date date, long hoursToAdd) {
        return plus(date, Duration.ofHours(hoursToAdd));
    }

    public static Date plusMinutes(Date date, long minutesToAdd) {
        return plus(date, Duration.ofMinutes(minutesToAdd));
    }

    public static Date plusSeconds(Date date, long secondsToAdd) {
        return plus(date, Duration.ofSeconds(secondsToAdd));
    }

    public static Date plusMillis(Date date, long millisToAdd) {
        return plus(date, Duration.ofMillis(millisToAdd));
    }

    private static Date set(Date date, int calendarField, int amount) {
        Assert.notNull(date, "The date must not be null");
        Calendar c = Calendar.getInstance();
        c.setLenient(false);
        c.setTime(date);
        c.add(calendarField, amount);
        return c.getTime();
    }

    public static Date plus(Date date, TemporalAmount amount) {
        Instant instant = date.toInstant();
        return Date.from(instant.plus(amount));
    }

    public static Date minus(Date date, TemporalAmount amount) {
        Instant instant = date.toInstant();
        return Date.from(instant.minus(amount));
    }

    public static Date minusYears(Date date, int years) {
        return set(date, 1, -years);
    }

    public static Date minusMonths(Date date, int months) {
        return set(date, 2, -months);
    }

    public static Date minusWeeks(Date date, int weeks) {
        return minus(date, Period.ofWeeks(weeks));
    }

    public static Date minusDays(Date date, long days) {
        return minus(date, Duration.ofDays(days));
    }

    public static Date minusHours(Date date, long hours) {
        return minus(date, Duration.ofHours(hours));
    }

    public static Date minusMinutes(Date date, long minutes) {
        return minus(date, Duration.ofMinutes(minutes));
    }

    public static Date minusSeconds(Date date, long seconds) {
        return minus(date, Duration.ofSeconds(seconds));
    }

    public static Date minusMillis(Date date, long millis) {
        return minus(date, Duration.ofMillis(millis));
    }

    public static Date minusNanos(Date date, long nanos) {
        return minus(date, Duration.ofNanos(nanos));
    }

    public static Date toDate(LocalDateTime dateTime) {
        return Date.from(toInstant(dateTime));
    }


    /**
     * 将字符格式转为Date
     * @param dateStr
     * @return
     */
    public static Date toDateTime(String dateStr) {
        if (U.isEmpty(dateStr)){
            return null;
        }
        for (int i = 0, size = dateTemplates.length; i < size; i++) {
            String templatePattern = dateTemplates[i];
            try {
                SimpleDateFormat format = new SimpleDateFormat(templatePattern);
                return format.parse(dateStr);
            } catch (Exception e) {
                continue;
            }
        }
        throw new ServiceException(dateStr + " 日期格式错误，仅支持的格式为：" + JSON.toJSONString(dateTemplates));
    }

    public static Date toDate(String dateStr, String format) throws ParseException {
        SimpleDateFormat formatter = new SimpleDateFormat(format);
        return formatter.parse(dateStr);
    }


    public static LocalDateTime toLocalDateTime(String dateStr) {
        if (U.isEmpty(dateStr)){
            return null;
        }
        for (int i = 0, size = dateTemplates.length; i < size; i++) {
            String templatePattern = dateTemplates[i];
            try {
                DateTimeFormatter dtf2 = DateTimeFormatter.ofPattern(templatePattern);
                return LocalDateTime.parse(dateStr,dtf2);
            } catch (Exception e) {
                continue;
            }
        }
        throw new ServiceException(dateStr + " 日期格式错误，仅支持的格式为：" + JSON.toJSONString(dateTemplates));
    }


    public static LocalDateTime toLocalDate(final Date date) {
        return LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault());
    }

    private static Instant toInstant(LocalDateTime dateTime) {
        return dateTime.atZone(ZoneId.systemDefault()).toInstant();
    }

    public static Duration between(Date startDate, Date endDate) {
        return Duration.between(startDate.toInstant(), endDate.toInstant());
    }


    /**
     * 生成id
     *
     * @return
     */
    public static long getId() {
        String format = format(new Date(), DateUtils.YMDHMSS);
        long randomNum = getRandomNum(2);
        String result = format + randomNum;
        return Long.valueOf(result);
    }


    /**
     * 获取随机数
     *
     * @param n
     * @return
     */
    protected static long getRandomNum(int n) {
        if (n < 1) {
            return 0;
        }
        double v = Math.random() * 9 * Math.pow(10, n - 1);
        double pow = Math.pow(10, n - 1);
        return (long) (v + pow);
    }


    /**
     * 获取两个日期时间的时长
     *
     * @param startDte
     * @param endDate
     * @return
     */
    public static String getDuration(Date startDte, Date endDate) {
        if (U.haveEmpty(startDte, endDate)) {
            return "";
        }
        long seconds = between(startDte,endDate).getSeconds();
        return getDuration(seconds);
    }

    public static String getDuration(long seconds) {
        long  minute = 60, hour = minute * 60, day = hour * 24;
        List<String> list = new ArrayList<>();
        list.add(value(seconds / day, "天"));
        list.add(value((seconds % day) / hour, "小时"));
        list.add(value((seconds % day % hour) / minute, "分钟"));
        list.add(value(seconds % day % hour % minute, "秒"));
        return String.join("", list);
    }

    private static String value(long number, String timeStr) {
        return number < 1 ? "" : number + timeStr;
    }


    /**
     * 获取两个日期之间的天
     *
     * @param startDte
     * @param endDate
     * @return
     */
    public static long getDay(Date startDte, Date endDate) {
        return between(startDte, endDate).toDays();
    }


    /**
     * 根据类型获取分割出来的 年|月|日|时|分|秒|豪秒
     *
     * @param date
     * @param type [年:Calendar.YEAR,
     *             月:Calendar.MONTH,
     *             日: Calendar.DAY_OF_MONTH,
     *             时:Calendar.HOUR_OF_DAY,
     *             分: Calendar.MINUTE,
     *             秒:Calendar.SECOND,
     *             豪秒:Calendar.MILLISECOND]
     * @return 2020|12|31|23|59|59|999
     */
    public static int cutDateX(Date date, int type) {
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(date);
        if (type == Calendar.MONTH) {
            return calendar.get(type) + 1;
        }
        return calendar.get(type);
    }


    /**
     * 获取总分钟数
     *
     * @param Hsm 14:25:32
     * @return
     */
    public static Long getMinute(String Hsm) {
        Long[] hms = cutArr(Hsm);
        return hms[0] * 60 + hms[1];
    }

    /**
     * 获取总秒数
     *
     * @param Hsm 14:25:32
     * @return
     */
    public static Long getSecond(String Hsm) {
        Long[] hms = cutArr(Hsm);
        return hms[0] * 60 * 60 + hms[1] * 60 + hms[2];
    }


    private static Long[] cutArr(String Hsm) {
        if (StringUtils.isBlank(Hsm)) {
            return new Long[]{0L, 0L, 0L};
        }
        String[] split = Hsm.split(":");
        if (split.length < 3) {
            throw new IllegalArgumentException("参数时间格式不正确，正确格式应为：14:25:32，传入的格式为：" + Hsm);
        }
        Long[] resultArr = new Long[3];
        resultArr[0] = Long.valueOf(split[0]);
        resultArr[1] = Long.valueOf(split[1]);
        resultArr[2] = Long.valueOf(split[2]);
        return resultArr;
    }


    /**
     * 获取日期拼接新时间
     *
     * @param date
     * @param Hms
     * @return
     */
    public static Date dateJoinTime(Date date, String Hms) {
        Long totalSecond = getSecond(Hms);
        Date newDate = beginTimeOfDay(date);
        return plusSeconds(newDate, totalSecond);
    }

    /**
     * 获取日期的开始时间
     */
    public static Date beginTimeOfDay(Date date) {
        Calendar cal = getCalendar(date);
        cal.set(Calendar.HOUR_OF_DAY, 0);
        cal.set(Calendar.MINUTE, 0);
        cal.set(Calendar.SECOND, 0);
        cal.set(Calendar.MILLISECOND, 0);
        return cal.getTime();
    }

    /**
     * 获取日期的结束时间
     */
    public static Date endTimeOfDay(Date date) {
        Calendar cal = getCalendar(date);
        cal.set(Calendar.HOUR_OF_DAY, 23);
        cal.set(Calendar.MINUTE, 59);
        cal.set(Calendar.SECOND, 59);
        cal.set(Calendar.MILLISECOND, 0);
        return cal.getTime();
    }


    /**
     * 获取周的开始时间
     * @return
     */
    public static Date beginOfWeek(Date date) {
        Calendar cal =  getCalendar(date);
        int dayofweek = cal.get(Calendar.DAY_OF_WEEK);
        if (dayofweek == 1) {
            dayofweek += 7;
        }
        cal.add(Calendar.DATE, 2 - dayofweek);
        return beginTimeOfDay(cal.getTime());
    }

    /**
     * 获取周的结束时间
     * @param date
     * @return
     */
    public static Date endOfWeek(Date date){
        Calendar cal = getCalendar(beginOfWeek(date));
        cal.add(Calendar.DAY_OF_WEEK, 6);
        Date weekEndSta = cal.getTime();
        return endTimeOfDay(weekEndSta);
    }


    private static Calendar getCalendar(Date date) {
        Calendar cal = Calendar.getInstance();
        if (U.notEmpty(date)) {
            cal.setTime(date);
        }
        return cal;
    }



    /**
     * 获取月的开始时间
     * @return
     */
    public static Date beginOfMonth(Date time){
        Calendar calendar = getCalendar(time);
        calendar.set(calendar.get(Calendar.YEAR), calendar.get(Calendar.MONTH) , 1);
        return beginTimeOfDay(calendar.getTime());
    }

    /**
     * 获取月的结束时间
     * @return
     */
    public static Date endOfMonth(Date time) {
        Calendar calendar = getCalendar(time);
        calendar.set(calendar.get(Calendar.YEAR), calendar.get(Calendar.MONTH) , 1);
        int day = calendar.getActualMaximum(Calendar.DAY_OF_MONTH);
        calendar.set(calendar.get(Calendar.YEAR), calendar.get(Calendar.MONTH) , day);
        return endTimeOfDay(calendar.getTime());
    }


    public static Integer week(Date date) {
        Calendar calendar =getCalendar(date);
       return calendar.get(Calendar.WEEK_OF_YEAR);
    }

    /**
     * 获取年的开始时间
     * @param date
     * @return
     */
    public static Date beginOfYear(Date date) {
        Calendar calendar =getCalendar(date);
        calendar.add(Calendar.YEAR, 0);
        calendar.add(Calendar.DATE, 0);
        calendar.add(Calendar.MONTH, 0);
        calendar.set(Calendar.DAY_OF_YEAR, 1);
        return beginTimeOfDay(calendar.getTime());
    }

    /**
     * 获取年的结束时间
     * @param date
     * @return
     */
    public static Date endOfYear(Date date) {
        Calendar calendar =getCalendar(date);
        int currentYear =calendar.get(Calendar.YEAR);
        calendar.clear();
        calendar.set(Calendar.YEAR, currentYear);
        calendar.roll(Calendar.DAY_OF_YEAR,-1);
        return endTimeOfDay(calendar.getTime());
    }


    
    /**
     * 获取时分秒格式
     *
     * @param minute
     * @return
     */
    public static String getHms(long minute) {
        Integer day =24;
        long h = minute / 60;
        if (h >= day) {
            h = h % 24;
        }
        long m = minute % 60;

        StringBuilder Hms = new StringBuilder();
        Hms.append(h < 10 ? "0" + h : h).append(":");
        Hms.append(m < 10 ? "0" + m : m).append(":");
        Hms.append("00");

        return Hms.toString();
    }



    /**
     * 去除冗余的时间
     *
     * @param dateList
     * @param startDate
     * @param endDate
     * @return
     */
    public static List<DateVo> removeRedundancyDate(List<DateVo> dateList, Date startDate, Date endDate) {
        List<DateVo> dateVoList = new ArrayList<>();
        if (U.haveEmpty(startDate, endDate, dateList)) {
            dateVoList.add(new DateVo(startDate, endDate));
            return dateVoList;
        }

        dateList = dateList.stream().sorted(Comparator.comparing(DateVo::getStartDate)).collect(Collectors.toList());

        int diffMillisecond = 1000;

        for (int i = 0; i < dateList.size(); i++) {
            if (U.haveEmpty(dateList.get(i).getStartDate(), dateList.get(i).getEndDate())) {
                continue;
            }

            if (i == 0) {
                if (startDate.getTime() < dateList.get(0).getStartDate().getTime()) {
                    if (endDate.getTime() <= dateList.get(0).getStartDate().getTime()) {
                        if (endDate.getTime() - startDate.getTime() > diffMillisecond) {
                            dateVoList.add(new DateVo(startDate, endDate));
                        }
                    } else {
                        if (dateList.get(0).getStartDate().getTime() - startDate.getTime() > diffMillisecond) {
                            dateVoList.add(new DateVo(startDate, dateList.get(0).getStartDate()));
                        }
                    }
                }
            }

            if (i > 0 && i < dateList.size() - 1) {
                if (startDate.getTime() <= dateList.get(i).getEndDate().getTime()) {
                    if (endDate.getTime() <= dateList.get(i + 1).getStartDate().getTime()) {
                        if (endDate.getTime() - dateList.get(i).getEndDate().getTime() > diffMillisecond) {
                            dateVoList.add(new DateVo(dateList.get(i).getEndDate(), endDate));
                        }
                    } else {
                        if (dateList.get(i + 1).getStartDate().getTime() - dateList.get(i).getEndDate().getTime() > diffMillisecond) {
                            dateVoList.add(new DateVo(dateList.get(i).getEndDate(), dateList.get(i + 1).getStartDate()));
                        }
                    }

                } else {
                    if (endDate.getTime() <= dateList.get(i + 1).getStartDate().getTime()) {
                        if (endDate.getTime() - startDate.getTime() > diffMillisecond) {
                            dateVoList.add(new DateVo(startDate, endDate));
                        }
                    } else {
                        if (dateList.get(i + 1).getStartDate().getTime() - startDate.getTime() > diffMillisecond) {
                            dateVoList.add(new DateVo(startDate, dateList.get(i + 1).getStartDate()));
                        }
                    }
                }
            }

            if (i == dateList.size() - 1) {
                if (endDate.getTime() > dateList.get(dateList.size() - 1).getEndDate().getTime()) {
                    if (startDate.getTime() < dateList.get(dateList.size() - 1).getEndDate().getTime()) {
                        if (endDate.getTime() - dateList.get(dateList.size() - 1).getEndDate().getTime() > diffMillisecond) {
                            dateVoList.add(new DateVo(dateList.get(dateList.size() - 1).getEndDate(), endDate));
                        }
                    } else {
                        if (endDate.getTime() - startDate.getTime() > diffMillisecond) {
                            dateVoList.add(new DateVo(startDate, endDate));
                        }
                    }
                }
            }
        }

        return dateVoList;
    }

    public static LocalDate randomDate(int year, int rangeInYears) {
        Random random = new Random();
        LocalDate startDate = LocalDate.of(year, 1, 1);
        long randomDays = random.longs(0, ChronoUnit.DAYS.between(startDate, startDate.plusYears(rangeInYears))).findFirst().getAsLong();
        return startDate.plusDays(randomDays);
    }


    /**
     * @author zhangnx
     */
    @Data
    @AllArgsConstructor
     static class DateVo {
        private Date startDate;
        private Date endDate;
    }

}

